﻿/*
DAPMX core 0.2.0
Copyright 2010 Maxim Sajin <joox@dapmx.org>
Regarding licensing information please refer http://dapmx.org
*/

/// dap launch

if(!window.onload)window.onload=function(){dapDocument(document.body,daQueryString())};

function dapDocument(root,data){
	var ns;
	// process inline namespaces
	for(var docns=document.getElementsByTagName("namespace"),i=docns.length;i-->0;)
		if(ns=docns[i].parentNode.removeChild(docns[i]))
			ns=daNamespace(
				isIE?daParseXML(ns.outerHTML):ns, // in IE, markup is re-parsed to get rid of fancy property-attributes
				ns.getAttribute("dapns")||window.location.href
			);
	if(ns)dapnsStack.unshift(ns);
	
	// activate dap root node
	var stub=newElemText(root.tagName,"loading..."),place=root.parentNode;
	dapnode=place.replaceChild(stub,root);
	daImportNS(dapnsStack[0],root.getAttribute("dapns-import"));
	place.replaceChild(daTemplate(dapnode).spawn(place,[{'':data}]),stub);
};


var	_DAP_ERRORS=[],			// dap errors are logged into this array
	daDEFAULT_EVENT="click",
	daRESERVED={			// dap-reserved attribute names. please, don't misuse them
		"dapns":true,		// namespace URI
		"dapns-import":true,	// imported namespaces
		"dapmodel":true		// element model override
	};
	
function daWarn(reason,subj){		// small errors will only log into _DAP_ERRORS
	_DAP_ERRORS.push({subj:subj,reason:reason,context:daContexts[0]});daTitle("DAP ERRORS: "+_DAP_ERRORS.length);
};
function daFail(reason,subj){		// severe errors will terminate execution
	daWarn(reason,subj);
	throw "dap error <"+subj+"> :: "+reason;//new Error(,false,0)
};


var dap={

	convert	:{
		""	:function(value){return String(value)},
		copy	:function(value){return daCopy(value)},
		
		esc	:function(value){return escape(value)},		/// escape
		usc	:function(value){return unescape(value)},	/// unescape
		
		xmlenc	:function(value){if(value)return value.replace(/\&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gr;")},
		absurl	:function(value){if(value)return daAbsUrl(value)},
		fname	:function(value){if(value)return value.split("/").pop()},
		ext	:function(value){if(value)return value.split(".").pop()},	/// file extension
		num	:function(value){return String(value).replace(/\B(?=(...)*$)/g," ")},		/// digit groups
		"~num"	:function(value){return value.replace(/\D/g,"")||0},
				
		"?"	:function(bool){return bool?true:false},	/// test
		"!"	:function(bool){return bool?false:true},	/// test inverse
		"#"	:function(bool){return bool?"+":"-"},		/// test as +/-
		
		"+?"	:function(num){return parseFloat(num)>0},	/// test positive
		"-?"	:function(num){return parseFloat(num)<0},	/// test negative
		"0?"	:function(num){return parseFloat(num)==0},	/// test zero
		
		"+"	:function(num){return (num=parseFloat(num))?String(Math.abs(num)):""},	/// abs
		"-"	:function(num){return (num=parseFloat(num))?String(-num):""},		/// neg
		"~"	:function(num){return (num=parseFloat(num))?(num>0?"+":"-"):"0"},	/// sgn
		
		space	:function(value){return daSplit(daTrim(value)," ")},
		lf	:function(value){return daSplit(value,"\n")},				///(line feed)
		csv	:function(value){return daSplit(value,",")},				/// x1,x2,x3 (comma-separated values)
		bar	:function(value){return daSplit(value,"|")},				/// x1|x2|x3 (bar-separated values)
		nvp	:function(value){return daSplit(daSplit(value,";"),":")},		/// x1:y1:z1;xN:yN:zN (named after "name:value"-pairs)
		lfc	:function(cdata){return daSplit(daSplit(daTrim(cdata),/\n\s*/),/\s*:/)},/// linefeed-colon

		xml2dom	:function(value){return daParseXML(value)},
		dom2xml	:function(value){return daSerialize(value)},
		obj2dom	:function(value){return daDumbXML(value,"object")},
		obj2xml	:function(value){return daSerialize(daDumbXML(value,"object"))},
		prettyprint:function(value){return daPrettyPrint(value)},
		
		dict	:function(value){return daModel(value,dap.model.dict)},
		flow	:function(value){return daModel(value,dap.model.flow)},
		rset	:function(value){return daModel(value,dap.model.rset)},
		book	:function(value){return daModel(value,dap.model.book)},
		"dict*"	:function(value){return daModel(value,dap.model["dict*"])},
		"flow*"	:function(value){return daModel(value,dap.model["flow*"])},
		dapns	:function(value){return daModel(value,dap.model.proto)},

		GET	:function(value){return daRequest(dap.method.GET,true,value)},

		eval	:function(value){return eval(value)}
	},
	
	flatten	:{
		"" 	:function(feed){	// flat hash {@a:v1,@b:v2}
				var hash={};
				for(var i=0;i<feed.length;i++)hash[feed[i].alias]=feed[i].value;
				return hash;	
			},
			
		"*"	:function(feed){	// pre-rowset
				var rows=[];
				for(var i=0;i<feed.length;i++){
					var t=feed[i], alias=t.alias, value=t.value, isRowset=value instanceof Array;
					if(alias)rows=rows.concat(daMapGrid(isRowset?value:[value],alias));
					else if(isRowset)rows=rows.concat(value);
					else rows.push(value);
				}
				return rows;
			},
			
		"~"	:function(feed){return feed},
		
		"%"	:function(feed){	/// php-like $entry substitution
				for(var head=feed.shift().value,i=0;i<feed.length;i++)head=daTuck(head,feed[i].alias,feed[i].value);
				return head.replace(/\{.*\}/g,'');
			},
			
		
			
		"+"	:function(feed){var sum=0;for(var i=0;i<feed.length;i++)sum+=parseFloat(feed[i].value);return String(sum)},
		"-"	:function(feed){var sub=parseFloat(feed[0].value)*2;for(var i=0;i<feed.length;i++)sub-=parseFloat(feed[i].value);return String(sub)},

		any	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value)return feed[i].value;return false},		/// first non-empty
		all	:function(feed){for(var i=0;i<feed.length;i++)if(!feed[i].value)return false; return feed[i-1].value},			/// succeeds if empty token found
		none	:function(feed){return !dap.flatten.any(feed)},
		lack	:function(feed){return !dap.flatten.all(feed)},

		"?@"	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value==feed[i].alias)return true;return false},	/// any match
		"!@"	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value!=feed[i].alias)return false;return true},	/// all match
		
		eq	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value!=feed[0].value)return false;return true},
		ne	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value!=feed[0].value)return true;return false},
		dsc	:function(feed){var a=parseInt(feed[0].value);for(var i=0;i<feed.length;i++)if(a<(a=parseInt(feed[i].value)))return false;return true},
		asc	:function(feed){var a=parseInt(feed[0].value);for(var i=0;i<feed.length;i++)if(a>(a=parseInt(feed[i].value)))return false;return true},
		
		str	:function(feed){var head=feed.shift(); return String[head.alias].apply(String(head.value),feed); },
		like	:function(feed){return feed.shift().value.indexOf(feed.shift().value)==0},

		
		concat	:function(feed){var string="";for(var i=0;i<feed.length;i++)if(feed[i].value)string+=feed[i].value;return string},
		space	:function(feed){return daConcat(feed," ")},
		csv	:function(feed){return daConcat(feed,",")},	/// x1,x2,x3 (comma-separated values)
		bar	:function(feed){return daConcat(feed,"|")},	/// x1|x2|x3 (bar-separated values)
		nvp	:function(feed){return daFlattenRowsets(feed,":",";")},
		
		url	:function(feed){return daURLEncode(feed)},
		GET	:function(feed){return daRequest(dap.method.GET,true,null,feed)},
		QUE	:function(feed){return daQueuery(feed)},
		
		sibling	:function(feed){
				for(var node=feed.shift().value,i=0;i<feed.length;i++)node=daSibling(feed[i].value,feed[i].alias,node);
				return node;
			},
			
		next	:function(feed){
				for(var head=feed.shift().value,a,i=0;i<feed.length;i++)if(feed[i].value==head)return feed[(i+1)%feed.length].value;
				return feed[0].value;
			},
			
		"pull"	:function(feed){for(var head=feed.shift().value,a,i=0;i<feed.length;i++)if((a=feed[i].value)&&(a=head[a]))return a},
		"case"	:function(feed){for(var head=feed.shift().value,i=0;i<feed.length;i++)if(feed[i].alias==head)return(feed[i].value)},
			
		"pull*"	:function(feed){
				var head=feed.shift().value, a, out=[];
				for(var i=0;i<feed.length;i++)
					if((a=feed[i].alias)){if((a=feed[i].value)&&(a=head[a]))out.push(a)}
					else if(!out.length)out.push(feed[i].value);
				return out;
			},
			
		"case*"	:function(feed){
				var head=feed.shift().value, a, out=[];
				for(var i=0;i<feed.length;i++)
					if((a=feed[i].alias)){if(a==head)out.push(feed[i].value)}
					else if(!out.length)out.push(feed[i].value);
				return out;
			}
	},
 
	operate	:{	/// see dap operators reference at http://dapmx.org/0.2.0/#operate
	
		"!"	:function(value,alias,node,$) {daPhase.d(node,value,$)},
		"%"	:function(value,alias,node,$) {if(alias)value=daMap(value,alias.split(","));for(var i in value)$[0][''][i]=value[i]},
		map	:function(value,alias,node,$) {$[0][''][alias]=daRoute(node,value||alias)},
		"^"	:function(value,alias,node,$) {
				for(var e=alias.split(","),i=e.length;i-->0;)
					for(var entry=e[i],a=null,D=$;D&&!a;D=D[1])
						if((a=D[0][''][entry])!=null)$[0][''][entry]=a;
			},

		"?"	:function(value,alias,node) {if(!value)return O},
		"??"	:function(value,alias,node) {if(alias?alias!=value:!value)return O},
		"*"	:function(value,alias,node) {return value?daMapGrid((value instanceof Array)?value:[value],alias):O},
		
		u	:function(value,alias,node) {daActivateNode(node,alias,value)},
		ui	:function(value,alias,node) {daActivateNode(node,alias,value,"ui")},
		"u!"	:function(value,alias,node) {if(value==null)value=node;if(value)daPhase.u(value,alias||daDEFAULT_EVENT)},
		attr	:function(value,alias,node) {if(value)node.setAttribute(alias,value)},
		
		"+"	:function(value,alias,node) {alert("+")},//var a=D[alias];if(!a||a==O)return[value];if(!(a instanceof Array))return[a,value];a.push(value);return a},//
		
		"+row"	:function(value,alias,node) {if(value)daSibling(value,alias,node)},
		"-row"	:function(value,alias,node) {if(value)daMoveRow(null,'away',value)},
		move	:function(value,alias,node) {daMoveRow(value,alias,node)},
		append	:function(value,alias,node) {
				var 	$=node.$[0][''],
					rows=alias?($[alias]||($[alias]=[])):daLocateRow(node).rows;
				rows.push(value);
			},
		
		go	:function(value,alias,node) {if(value)return alias},
		done	:function(value,alias,node) {return (!alias)||value}//false;
	},
	
	model	:{	/// data parsing models
		dict	:daL([daDict]),		// one-level dictionary
		flow	:daL([daFlow]),		// one-level sequence
		
		"dict*"	:daL([daDict,true]),	// recursive dictionary
		"flow*"	:daL([daFlow,true]),	// recursive sequence
		
		rset	:daL([daFlow,daDict,true]),		// rowset (sequence of dictionaries)
		book	:daL([daDict,daFlow,daDict,true]),	// dictionary of rowsets
		
		proto	:daL([daTemplate])	// prototype
	},

	method	:{	/// XHR request methods

		GET	:function(req,async,url,feed,contentType){
				req.open("GET", (url||'')+daURLEncode(feed), async);
				req.setRequestHeader("Content-Type",contentType||"text/xml");
				req.send(null);
			},
			
		POST	:function(req,async,url,feed,contentType){
				// if contentType not specified, data is urlencoded; otherwise sent as is
				var post = contentType ? feed : daURLEncode(feed);
				req.open("POST",url,async);
				req.setRequestHeader("Content-Type",contentType||"application/x-www-form-urlencoded");
				req.setRequestHeader("Content-Length",post?post.length:0);
				req.send(post);
			},
			
		QUEUERY	:function(req,async,url,feed,queryType){
				var queuery=newNode("queuery"),a;
				for(var i=0;i<feed.length;i++)
					try{ queuery.appendChild(
						feed[i].alias
						?daDumbXML(feed[i].value,feed[i].alias)
						:daParseXML(feed[i].value)
						)
					}
					catch(e){daFail("bad query: "+feed[i].alias,feed[i].value)}
				post=daSerialize(queuery);
				req.open("POST",url,async);
				req.setRequestHeader("Content-Type","text/xml");
				req.setRequestHeader("Content-Length",post?post.length:0);
				req.setRequestHeader("dap-Query-Type",queryType||"Queuery");
				req.send(post);
			}
	}
};


/// postprocess - handle a node after it is inserted into visible DOM
var dapost=[];
function daPostProcess(subj,handler){dapost.push({handler:handler,subj:subj})};


/// handy stuff, that may also be useful for custom namespaces

var isIE=(navigator.appName == 'Microsoft Internet Explorer');	//i hate MSIE

function daTitle(text){document.title=text};
function unslash(str){return str?str.replace(/\\n/g,"\n"):""};
								
function daQueryString(){		/// extract page arguments from its URL
	var nv,hash={},href=location.href.split("#"),hs=href[1],qs=href[0].split("?")[1];
	if(qs)for(var i in qs=qs.split("&"))nv=qs[i].split("="),hash[nv[0]]=decodeURI(nv[1]);
	if(hs)for(var i in hs=hs.split(";"))nv=hs[i].split(":"),hash[nv[0]]=decodeURI(nv[1]);
	return hash;
};

function daAbsUrl(url,base){		/// evaluate absolute URL from relative to base URL
	if(url==null)return;
	if(/^\w*:/.test(url))return url;
	
	var	a=location.href, //base||location.href,//
		protocol=(a=a.split("//"))[0],
		hostname=(a=a[1].split("/")).shift(),
		filename=a.pop(),
		pathname="/"+(a.length?(a.join("/")+"/"):"");
	
	var a=protocol+"//"+hostname;
	if(url[0]=="/")return a+url;
	a+=pathname.replace(/\/[^\/]*$/,'');
	var up=url.match(/\.\.\//g);
	if(!up)return a+"/"+url;
	a=a.split("/").slice(0,-(up.length));
	a.push(url.substr(up.length*3));
	return a.join("/");
};

function daURLEncode(feed){		/// convert feed to urlencoded string. anonymous tokens appended unencoded
	var url="",a,v;
	if(feed)for(var i=0;i<feed.length;i++)if(v=feed[i].value)url+=(a=feed[i].alias)?"&"+a+"="+encodeURIComponent(v):v;
	return url;
};


/// http request. method implementations are defined in dap.method section

var daASYNC=false; // async queries are only allowed on d-phase

function daRequest(method,async,url,feed,contentType){
	var req=window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Msxml2.XMLHTTP');
	async=async&&daASYNC&&!daContexts.length;
	if(async)req.onreadystatechange=xhrProgress;
	method(req,async,url,feed,contentType);
	if(async)throw new daPostpone(req);
	else return xhrGetXML(req);
};


/// async XHR querying

function daPostpone(req){
	this.snapshot={};
	if(isIE)ieXHRsucks.push(req={xhr:req,postpone:this});
	else req.postpone=this;
};
daPostpone.prototype={
	resume:function(value){
		var y=this.snapshot;
		if(y.execBranch){
			y.target.value=value;
			y.execBranch.exec(this.snapshot.todo);
		}
		if(y.queuery){
			y.queuery.dispense(value);
		}
	}
};
function xhrProgress(){
	if(this.readyState==4)
		((isIE?ieXHRsuck(this):this).postpone.resume(xhrGetXML(this)));
};
function xhrGetXML(req){
	var xml;
	if(req.status==200)try{xml=req.responseXML?req.responseXML.documentElement:req.responseText}catch(e){}
	else daWarn("Problem with URL: ["+req.daURL+"]\n",req);
	return xml||emptyXML;
};

var ieXHRsucks=[];
function ieXHRsuck(xhr){
	for(var i=ieXHRsucks.length;i-->0;)if(ieXHRsucks[i].xhr==xhr){var a=ieXHRsucks[i];ieXHRsucks.splice(i,1);return a}
};


/// Queuery framework

function daQueuery(){
	this.domains={};
	this.clients={};
};
daQueuery.prototype={

	quey	:0,	// query key

	append	:function(quequest,domain){
			domain=daAbsUrl(domain);
			quequest.query.setAttribute("quey",++quey);
			(this.queries[domain]||(this.queries[domain]=[])).push(quequest.query);
			(this.clients[quey])=quequest.postpone;
		},
	execute	:function(){
			for(var url in this.domains){
				try{daRequest(dap.method.POST,true,url,daSerialize(this.domains[url]),"text/xml")}
				catch(y){y.snapshot.queuery=this}
			}
		},
	dispense:function(response){
			for(var nodes=response.nodes,i=0;i<nodes.length;i++){
				var result=nodes[i], k=result.getAttribute("quekey");
				if(k)result.removeAttribute("quekey"),this.clients[k].postpone.resume(result);
			}
		}
}

function daQuequest(method,dummy,url,feed,contentType){
	daQueuery.append(this,url);
	this.query=newNode("query");
	method(this,async,url,feed,contentType);
	throw new daPostpone(req);
}
daQuequest.prototype={

	open	:function(dummy,url){this.query.setAttribute("url",url)},
	send	:function(post){if(post)this.query.appendChild(newCDATA(post))},
		
	setRequestHeader:
		function(name,value){
			var header=this.query.appendChild(newNode("header"));
			header.setAttribute("name",name);
			header.setAttribute("value",value);
		}
}

///

function daClone(value){
	if(value instanceof Object){
		var prop={};
		for(var i in value)if(value.hasOwnProperty(i))prop[i]=daClone(value[i]);
		prop.prototype=value.prototype;
	}else return value;
}


/// string works

function daTrim(str){			/// trim padding whitespaces
	if(str&&str.replace)return str.replace(/^\s+|\s+$/g,"")
};
function daReplaceMulti(value,regs){	/// multiple regex replaces
	if(value)for(var i=regs.length;i-->0;)value=value.replace(regs[i][0],regs[i][1]);
	return value;
};
function daTuck(str,alias,value){
	if(value instanceof Object && !(value instanceof String))for(var i in value)str=daTuck(str,i,value[i]);
	else str=str.replace(new RegExp('\{'+alias+'\}','g'),value);
	return str;
}
function daConcat(feed,separator){	/// concatenate feed tokens
	var a,out=[];
	for(var i=0;i<feed.length;i++)if(a=feed[i].value)out.push(a);
	return out.join(separator);
};
function daSplit(value,separator){	/// recursive split - string to rowset conversion
	if(value instanceof Array)for(var i=0;i<value.length;i++)value[i]=daSplit(value[i],separator);
	else if(value&&value.split)value=value.split(separator);
	return value;
};
function daFlattenRowsets(feed,fsep,rsep){
	var out=[];
	for(var i=0;i<feed.length;i++)if(feed[i]){
		var rows=feed[i].value, flds=feed[i].alias;
		if(flds)flds=flds.split(",");
		if(rows)for(var r=0;r<rows.length;r++){
			var row=rows[r], unrow=[];
			if(flds)for(var f=0;f<flds.length;f++)unrow.push(row[flds[f]]);
			else for(var f in row)unrow.push(row[f]);
			out.push(unrow.join(fsep));
		}
	}
	return out.join(rsep);
};


/// HTML node manipulation shortcuts
function newElem(e){return document.createElement(e)};
function newText(t){return document.createTextNode(t)};
function newElemText(e,t){var e=newElem(e);if(t)e.appendChild(newText(t));return e};
function newStub(c){return document.createComment(c)};
function insertBefore(newnode,refnode){if(!refnode)return;refnode.parentNode.insertBefore(newnode,refnode)};
function insertAfter(newnode,refnode){if(!refnode)return;if(refnode.nextSibling)refnode.parentNode.insertBefore(newnode,refnode.nextSibling);else refnode.parentNode.appendChild(newnode)};
function insertInstead(newnode,refnode){if(!refnode)return;refnode.parentNode.replaceChild(newnode,refnode)};
function daCut(node){return node.parentNode?node.parentNode.removeChild(node):node};
function enstyle(node,cls){cls+=" ";if(!node.className)node.className=cls;else if(node.className.indexOf(cls)<0)node.className+=" "+cls;return node};
function destyle(node,cls){if(node.className)node.className=node.className.replace(new RegExp("\\s*"+cls,"g"),"");return node};
daAddEventListener=
	document.addEventListener?function(node,event,handler){node.addEventListener(event,handler,false)}:
	//document.attachEvent?function(node,event,handler){node.attachEvent(event,handler,false)}:
	function(node,event,handler){node["on"+event]=handler};

	
/// ui highlight
function evStop(e){if(e&&e.stopPropagation)e.stopPropagation();else event.cancelBubble=true};
function daOver(e){evStop(e);enstyle(this,"over")};
function daOut (e){evStop(e);destyle(this,"over")};

function daActivateNode(node,alias,value,hilite){
	if(!alias)alias=daDEFAULT_EVENT;
	daAddEventListener(node,alias,daEvent);
	
	if(!value)value=node.P;
	if(!value.rules)value.engage();
	(node.rules||(node.rules={}))[alias]=value.rules.u;

	if(hilite&&node.className.indexOf(hilite+" ")<0){
		enstyle(node,hilite);
		daAddEventListener(node,"mouseover",daOver);
		daAddEventListener(node,"mouseout",daOut);
	}
}
function daEvent(e){
	if(!e)e=window.event;
	evStop(e);
	daPhase.u(this,e.type);
	return true;
}

/// XML(non-HTML) node shortcuts
function newNode(e){return XMLdocument.createElement(e)};
function newTextNode(t){return XMLdocument.createTextNode(t)};

var XMLdocument;
	if(window.DOMParser)XMLdocument=new DOMParser().parseFromString("<xml/>","text/xml");
	else {XMLdocument=new ActiveXObject("Microsoft.XMLDOM");XMLdocument.async=false;XMLdocument.loadXML("<xml/>");}

daParseXML=window.DOMParser		/// XML -> DOM
	?function(str){
		var x=new DOMParser().parseFromString(str,"text/xml").documentElement;
		return (x.nodeName!="parsererror")?x:newElemText("value",str);
	}
	:function(str,alt){
		var x=new ActiveXObject("Microsoft.XMLDOM");
		x.async=false;
		x.loadXML(str);
		return (x.parseError)?newElemText("value",str):x.documentElement; 
	};
	
daSerialize=window.XMLSerializer	/// DOM -> XML
	?function(node){return new XMLSerializer().serializeToString(node)}
	:function(node){
		var x=new ActiveXObject("Microsoft.XMLDOM");
		x.async="false";
		//x.loadXML(str);
		//return (x.parseError)?newElemText(alt,str):x.documentElement; 
	};
	
daPrettyPrint=window.XML
	?function(xml){return XML(xml).toXMLString()}
	:function(xml){return xml};

function daDumbXML(obj,tag){		/// build a simple xml document from obj hierarchy
	var node=newNode(tag);
	if(obj instanceof Object)for(var i in obj)node.appendChild(daDumbXML(obj[i],i));
	else if(obj)node.appendChild(newTextNode(String(obj)));
	return node;
};


// rowset works
function daMap(datarow,columns){
	var entry={};
	if(datarow instanceof Array)for(var i=datarow.length;i-->0;entry[columns[i]]=datarow[i]);
	else entry[columns[0]]=datarow;
	return entry;
};
function daMapGrid(grid,alias){
	if(!alias)return grid;
	var rowset=[];
	var columns=alias.split(",");
	for(var row in grid)rowset.push(daMap(grid[row],columns));
	return rowset;
};
function daLocateRow(node){
	var row=node.$[0][''],rows=node.$[2];
	while(rows==node.parentNode.$[2])node=node.parentNode;
	for(var i=rows.length; i>=0&&rows[i]!=row; i--);
	if(i>=0)return{ base:node, rows:rows, row:row, index:i };
};
function daMoveRow(value,alias,node){
	var r=daLocateRow(node);
	if(r){
		var base=r.base, row=r.row, rows=r.rows, i=r.index;
		switch(value){
			case'up'	:if(i>0) rows[i]=rows[i-1], rows[i-1]=row, insertBefore(base,base.previousSibling);break;
			case'down'	:if(i<rows.length-1) rows[i]=rows[i+1], rows[i+1]=row, insertAfter(base,base.nextSibling);break;
			case'top'	:rows.splice(i,1), rows.unshift(row), insertBefore(base,base.parentNode.firstChild);break;
			case'bottom'	:rows.splice(i,1), rows.push(row), insertAfter(base,base.parentNode.lastChild);break;
			case'away'	:rows.splice(i,1), base.parentNode.removeChild(base);
		}
	}
};
function daSibling(value,alias,node){
	var r=daLocateRow(node);
	if(r){
		var	base=r.base, rows=r.rows, i=r.index,
			sibling=node.P.spawn(base.parentNode,[{'':value},base.$,rows]);
		if(sibling)switch(alias){
			case "instead"	:rows[i]=value;insertInstead(sibling,base);break;
			case "before"	:rows.splice(i,0,value),insertBefore(sibling,base);break;
			case "after"	:
			default		:rows.splice(i+1,0,value),insertAfter(sibling,base);break;
		}
		return sibling;
	}
};


/// core data models
function daFlow(node,model){
	var flow=[],nodes=node.childNodes,a,m0,m1;
	if(model)
		if(model[0]==true)m0=daFlow,m1=model;
		else m0=model[0],m1=model[1];
	for(var a=node.attributes,i=0;i<a.length;i++)
		flow[t=a[i].nodeName.toLowerCase()]=a[i].nodeValue;
	if(nodes)for(var i=0;i<nodes.length;i++){
		var n=nodes[i];
		switch(n.nodeType){
			case 1:
				a=m0?m0(n,m1):n;
				flow.push(a);
				break;
			case 3:
			case 4:if((a=n.nodeValue).replace(spaces,""))flow.push(a);break;
		}
	}return flow;
};
function daDict(node,model){
	var dict={},nodes=node.childNodes,t=null,text="",a,m0,m1;
	if(model)
		if(model[0]==true)m0=daDict,m1=model;
		else m0=model[0],m1=model[1];
		
	for(var a=node.attributes,i=0;i<a.length;i++)
		dict[t=a[i].nodeName.toLowerCase()]=a[i].nodeValue;
	if(nodes)for(var i=0;i<nodes.length;i++){
		var n=nodes[i];
		switch(n.nodeType){
			case 1:	t=n.nodeName.toLowerCase();
				a=m0?m0(n,m1):n;
				if(dict[t])((dict[t]instanceof Array)?dict[t]:(dict[t]=[dict[t]])).push(a);
				else dict[t]=a;
				break;
			case 3:
			case 4:if((a=n.nodeValue).replace(spaces,""))text+=daTrim(a);break;
		}
	}
	if(!t)return text;
	if(text)dict[""]=text;
	return dict;
};
function daTemplate(node,inline){
	var content=daFlow(node,dap.model.proto);
	if(content.length<2)content=content.length?content[0]:null;
	return (inline&&!node.attributes.length)?content:new daProto(inline,node,content);
};

function daL(a){for(var i=a.length,L=[a[--i]];i-->0;L=[a[i],L]);return L};
function daModel(node,model){return model[0](node,model[1])};
function daPickModel(dapmodel,dapns){return daReachNS(dapmodel,dap.model,dapns)||daFail("Can't resolve model: "+dapmodel,dapns)};


/// uniform node notation support
function daUniform(node){ // convert classed node to dap uniform notation
	var tag=node.tagName.split("."),c=node.getAttribute("class");
	if(c)tag=tag.concat(c.split(" ")),node.removeAttribute("class");
	return tag;
};
function daNativeNode(tag){ // create a native (HTML) node from dap uniform notation
	var node=document.createElement(tag.shift());
	if(tag.length)node.className=tag.join(" ");
	return node;
};

var O=[], emptyPath=[''], emptyXML=document.createElement('empty'), spaces=/\s+/g;


//dap engine (compressed)
function daChain(chainstring){return chainstring.split(",")};
function daPath(pathstring){
	if(!pathstring)return emptyPath;
	var path=pathstring.split(".");
	return path.length>1?path.reverse():path;	
};

function daPut(obj,path,value){
	for(var i=path.length,alias=path[--i];i-->0;obj=obj[path[i]]||(obj[path[i]]={}));
	obj[alias]=value;
};

function daReach(path,o){for(var i=path.length;o&&(i-->0);o=o[path[i]]);return o};
function daRoute(o,str,value){
	var path=str?daPath(str):o;
	if(value==null)return str?daReach(path,o):o;
	if(path){
		var entry=path.shift();
		(path.length?daReach(path,o):o)[entry]=value;
	}
	return value;
};


/// namespaces

var	dapnsRegistry={}, dapnsStack=[{}], dapx={};

function daNS(href){
	this[""]=href;
	if(!dapnsRegistry[href])dapnsRegistry[href]=this;
	else daFail("duplicate namespace: "+href);
}

function daNamespace(node,href,base){

	//if(!node.getAttribute)alert("635:"+node);

	var	inherit=(href[0]=="#")?dapnsStack[0]:null,
		imports=node.getAttribute("dapns-import"),
		model,ihref,cdata="",a;
		
	if(inherit){
		ihref=inherit[""];
		href=ihref+href;
		for(var i in dap)if(a=dap[i][ihref])dap[i][href]=a;
	}
	else href=daAbsUrl(href,base);
	
	var	tgtns= dapnsRegistry[href] || new daNS(href), //target dapns
		refns=inherit||tgtns;
	
	// if(a=node.getAttribute("dapns-extend"))
		// a=daAbsUrl(a),
		// refns=tgtns=dapnsRegistry[href]=dapnsRegistry[a] || new daNS(a);
	
	if(imports)daImportNS(refns,imports);
	if(a=node.getAttribute("dapmodel"))model=daPickModel(a,refns);
	
	dapnsStack.unshift(refns); //reference dapns
	
	for(var a=node.attributes,t,i=a.length;i-->0;)tgtns[t=a[i].nodeName.toLowerCase()]=a[i].nodeValue;
	for(var nodes=node.childNodes,i=nodes.length;i-->0;){
		var n=nodes[i],alias;
		switch(n.nodeType){
			case 1:	alias=n.nodeName.toLowerCase();
				if(!tgtns[alias])tgtns[alias]=
					(a=n.getAttribute("dapns"))?daNamespace(n,daTrim(a),href):
					(a=n.getAttribute("dapmodel"))?daModel(n,daPickModel(a,refns)):
					model?daModel(n,model):
					daTemplate(n,true);
				else	if(a=n.getAttribute("dapns"))n=daNamespace(n,daTrim(a),href); 
					else daFail("Duplicate "+alias+" in "+href,tgtns);
				break;
			case 3:
			case 4: if(a=n.nodeValue)cdata+=daTrim(a);break;
		}
	}
	if(cdata&&!inherit)// no js in inheriter dapns
		try{
			if(a=eval(cdata))for(var i in a)if(dap[i]){
				var o=dap[i][href];
				if(o)
					for(var j in a[i])o[j]=a[i][j];
				else dap[i][href]=a[i];
			}
		}catch(e){daFail("Namespace "+href+" has invalid js",cdata)}
	
	dapnsStack.shift();
	
	return tgtns;
};

function daImportNS(dapns,imports){
	if(!imports)return;
	imports=daTrim(imports).replace(/\s*::\s*/g,"::").split(spaces);
	for(var i=0; i<imports.length; i++){
		var	a=imports[i].split("::"),
			alias=a[0], base=dapns.dapns,
			href=daAbsUrl(a[1],base),
			present=dapns[alias];
		if(!present)dapns[alias]=dapnsRegistry[href] || new daNS(href);
		else if(daAbsUrl(present.dapns,base)!=href)
			daFail("Duplicate '"+alias+"' in "+dapns.dapns+" : "+href,dapns);
	}
};
function daRequireNS(dapns){
	if(!dapns.dapns){ //if dapns have been loaded already, it should have had "dapns" attribute
		var	href=dapns[""]||daFail("No href for this entry",dapns),
			xml=daRequest(dap.method.GET,false,href,null,"text/xml") || daFail("Cannot load namespace:"+href);
		daNamespace(xml,href);
	}
	return dapns;
};
function daSeekNS(path,dapns){
	var alias=path.pop(), entry=dapns[alias]||daFail("Namespace not found: "+alias, dapns);
	if(entry[""])daRequireNS(entry);
	return path.length?daReach(path,entry):entry;
};
function daReachNS(pathstring,domain,dapns){
	var path=pathstring.split("."),alias=path[0],a;
	if(path.length>1){
		if(alias)dapns=daRequireNS(dapns[alias]||daFail("Namespace not found: "+alias,dapns));
		path[0]=dapns[""];
		return daReach(path.reverse(),domain);
	}else return (a=domain[dapns[""]])&&a[alias] || domain[alias];
};
function daChainReachNS(dapns,paths,domain,chain){
	for(var paths=daChain(paths),i=paths.length,a; i-->0; )
		if(a=daReachNS(paths[i],domain,dapns))chain=[a,chain];
		else return;
	return chain;
};


// Parse

var	daWS	=/[\s\r\n\t]+/g,
	daBOPN	=/[\(\{\<\[][\s;]*/g,
	daBCLS	=/[\s;]*[\]\>\}\)]/g,
	daJUNK	=new RegExp([
		"(?:\\s*/\\*.*?\\*/\\s*)",	//C-style comments
		"^(?:\\s|;)*",			//leading spaces
		"(?:\\s|;)+(?://.*)*$"	//trailing semicolons and comments to end-of-rule 
	].join("|"),"g"),
	
	daINHRT	=":: ",				//inherit rule
	daBRCKT	=/\(([^\(\)]*)\)/g,		//bracket contents
	daSTEPS	=/\s*;\s+/,
	daTOKEN =/,?\s+/,

	daBrackets=[],
	daContexts=[];

function daCutBracket(outer,inner){return"<"+daBrackets.push(inner)+">"};
function daPickBracket(key){return daContexts[0].bckts[parseInt(key.replace(/\D/g,""))]};


function daProto(inline,node,content){
	this.content	=content;
	this.dapns	=dapnsStack[0];
	this.xml	=node.cloneNode(false);
	if(!inline)	this.node=daNativeNode(daUniform(node));
};
daProto.prototype={

	engage:function(){
		daContexts.unshift({dapns:this.dapns});
		var a, chi=this.content&&[new daRunChildrenStep(this.content)];
		this.rules={
			u:(a=this.xml.getAttribute("u"))&&new daRule(a),
			d:((a=this.xml.getAttribute("d"))||chi)&&new daRule(a,chi)
		}
		daContexts.shift();
	},
	
	spawn:function(node,$){
		if(!this.rules)this.engage();
		var d=this.rules.d,empty;
		
		if(!$)$=node.$;
		
		if(this.node){
			node=this.node.cloneNode(false),
			node.$=d&&d.def?[{'':$[0]['']},$,$[2]]:$,
			node.P=this;
		}
		else if(d&&(d.use||d.def))daWarn('Entry references are discarded in inlines',this);
		
		if(d)empty=new daExecBranch(node).exec(this.rules.d.todo)&&!node.childNodes.length;
		if(this.node)return(empty&&!node.childNodes.length)?daMute(node):node;
	}
};
function daRunChildrenStep(content){
	this.operator=dap.operate["!"];
	this.feed=[{value:content}];
};
function daRule(rulestr,todo){

	if(rulestr){
		var	c=daContexts[0],
			a=daTrim(rulestr).split(daINHRT),
			inherit=a[1];
			
		if(a[1]){
			var inherit=daSeekNS(daPath(a[1]),c.dapns) || daFail("Can't inherit: "+rulestr,c.dapns);
			if(!inherit.rules)inherit.engage();
			var rule=inherit.rules.d;
			if(todo){
				for(var r=todo;r[1];r=r[1]);
				r[1]=rule.todo;
			}
			else todo=rule.todo;
			for(var i in rule.def)(this.def||(this.def={}))[i]=true;
			for(var i in rule.use)(this.use||(this.use={}))[i]=true;
		}
		if(a=a[0].replace(daWS," ").replace(daBOPN,'(').replace(daBCLS,')').replace(daJUNK,"")){
			if(a.indexOf("--")==0)alert("["+a+"]");
			c.rulestring=a;
			
			daBrackets=[];
			while(a!=(a=a.replace(daBRCKT,daCutBracket)));
			daBrackets.unshift(a);
			
			c.bckts=daBrackets;
			c.use=null;
			c.def=null;
			todo=daList(daStep,a.split(daSTEPS),todo);
			for(var i in c.def)if(i)(this.def||(this.def={}))[i]=true;
			for(var i in c.use)if(i)(this.use||(this.use={}))[i]=true;
		}
	}
	this.todo=todo;
};
function daStep(a){
	if(!a)return null;
	if(/^<\d*>$/.test(a))this.branch=daList(daStep,daPickBracket(a).split(daSTEPS),null);
	else{
		a=a.split(daTOKEN);
		if(!/[$<=]/.test(a[0])){// operator:convert@alias
			var h=a.shift();
			if((h=h.split("@")).length>1)this.alias=h[1];
			if((h=h[0].split(":")).length>1)this.convert=daConverts(h[1]);
			this.operator=(h[0]=="")?O:daReachNS(h[0],dap.operate,daContexts[0].dapns) ||daFail("missing operator "+h,daContexts[0].dapns);
		}
		this.feed=a.length?daVector(daToken,a.reverse(),this):[{alias:this.alias,convert:this.convert}];
	}
};
function daToken(a,step){

	if((a=a.split("`")).length>1)this.liter=a.length>2?unescape(a[2]):a[1];
	if((a=a[0].split("@")).length>1)this.alias=a[1];
	else if(step)this.alias=step.alias;
	
	var parts=a[0].split("=");
	
	//rvalue
	if(a=parts.pop())this.daLvalue(a,true,step&&step.convert);
	else this.liter=this.liter||'';
	
	if(this.liter&&this.convert)for(var convert=this.convert;convert=convert[1];)this.liter=convert[0](this.liter);
	
	//lvalues
	if(parts.length)this.lvalues=daList(daLvalue,parts);
	
	if(this.path==true) // $entry=. → $entry=.entry
		a=(this.lvalues||daFail("Illegal shorthand",this))[0].path,
		this.path=[a[0]||a[1]||daFail("Illegal shorthand",this),''];// ? this.path=a ?

	if(this.alias==null)this.alias=(this.path&&this.path[0]);
};
function daLvalue(a,use,convert){
	var c=daContexts[0],flatten=null;
	if((a=a.split(":")).length>1)convert=daConverts(a[1],convert||null);
	if((a=a[0].split(">")).length>1){
		flatten=!a[1]?dap.flatten[""]:daReachNS(a[1],dap.flatten,c.dapns)||daFail("missing flattener "+a[1],c);
		if((a=a[0].split("<"))[1])this.feed=daVector(daToken,daPickBracket(a[1]).split(daTOKEN).reverse());
	}
	if(flatten||convert)this.convert=[flatten,convert]; //flattener as 0-order convertor, to unify async resumes
	if(a=a[0])switch(a[0]){
	
		case".":this.path=(a==".")||daPath(a);break;
			
		case"$":a=daPath(a.substr(1));
			this.path=a;
			var entry=[a[a.length-1]];
			if(use)(c.use||(c.use={}))[entry]=true;
			else if(a.length==1)(c.def||(c.def={}))[entry]=true;
			break;
			
		case"#":if(a=a.substr(1))switch((a=daPath(a)).pop()){
				case''		:this.path=a;a.push(null);break;
				case'content'	:this.liter=this.content;break;
				default		:daFail('unknown mesh',this);
			}else this.path=[null];
			break;
			
		default:a=daPath(a);
			if(this.alias==null)this.alias=a[0];
			this.value=daSeekNS(a,c.dapns);
	}
};
daToken.prototype.daLvalue=daLvalue;

function daVector(daClass,a,step){
	if(!a.length)return null;
	for(var i=a.length;i-->0;)a[i]=new daClass(a[i],step);
	return a;
};
function daList(daClass,a,tail){
	for(var i=a.length;i-->0;)tail=[new daClass(a[i]),tail];
	return tail;
};
function daConverts(a,tail){
	return daChainReachNS(daContexts[0].dapns,a,dap.convert,tail) ||daFail("missing converter "+a,daContexts[0]);
};
function daMute(node){var n=newStub(node.nodeName);n.P=node.P;n.$=node.$;return n};


// Execute
var daStackDepth=0;

function daExecBranch(node,$,up){
	this.node=node;
	this.$=$||node.$;
	this.up=up;
};
daExecBranch.prototype={

	exec:
	function(todo){
		try{
			return this.execBranch(todo);
		}
		catch(y){
			if(y.snapshot)y.snapshot.execBranch=this;
			else throw y; //daFail("some error",y);	// regular error
		}
	},

	execBranch: //returns true if empty
	function(todo){
		if(++daStackDepth>100)daFail("Suspicious recursion depth",this);
		try{
			var node=this.node, $=this.$, empty=true;
			while(todo){
				var step=todo[0],result=null;
				if(step.branch)new daExecBranch(node,null,this.up).execBranch(step.branch);// throws {branch}
				else{
					var	operator=step.operator,
						feed=this.execFeed(step.feed,[]); //throws {feed}
					if(operator)
						for(var i=0;i<feed.length;i++)
							if(result=operator(feed[i].value,feed[i].alias,node,$))
								if(result instanceof Array)
									for(var r=0;r<result.length;r++)
										empty=new daExecBranch(node,[{'':result[r]},$,result],this.up).exec(todo[1])&&empty;
				}
				todo=!result&&todo[1];
			}
		}
		catch(y){
			if(y.snapshot)
				y.snapshot={
					target	:y.snapshot.target,
					todo	:[
							{
								branch:y.snapshot.todo,
								operator:operator,
								feed:y.snapshot.token.feed,
							},
							todo[1]
						]
				}
			throw y;
		}
		daStackDepth--;
		return result&&(result instanceof Array)?empty:result;
	},

	execFeed:
	function(feed,collect,flatten,proto)
	{
		var i=feed.length;
		try	{
			if(collect){
				while(i-->0)collect.push({alias:feed[i].alias,value:this.execToken(feed[i])}); // throws {token}
				if(flatten)collect=flatten(collect); //throws {async}
				if(proto)daPhase.d(this.node,proto,[{'':collect},this.$,this.$[2]]);
			}
			else while(i-->0)this.execToken(feed[i]); // throws {token};
			return collect;
		}
		catch(y){
			if(y.snapshot){
				if(!y.snapshot.target)	//async flattener
					y.snapshot.target=y.snapshot.token={proto:proto};
				else{ //async exectoken
					if(!collect)collect=[];
					collect.push(y.snapshot.token);
					while(i-->0)collect.push(feed[i]);
					y.snapshot.token={
						feed:collect,
						flatten:flatten,
						proto:proto
					}
				}
			}
			throw y;
		}
	},
	
	execToken:
	function(token)
	{
		var value=token.value,a,c;
		
		if(a=token.path){
			var i=a.length, entry=a[--i];
			if(entry){
				if(this.up){
					value=this.up[entry];
					if(value==null)for(var $=this.$;$&&(value=$[0][entry])==null;)$=$[1];
				}
				else value=daTrace(this.$,entry);
				if(value==null)daFail("Entry not found: "+entry,this);
			}
			else value = (entry==null) ? this.node : this.$[0][''];
			while(value&&(i-->0))value=value[a[i]];
		}
		
		try	{
			var lvalues=token.lvalues,convert,flatten;
			convert=token.convert, flatten=token.feed&&convert[0];
			if(flatten)value=this.execFeed(token.feed,[],flatten,value||token.value);//throws{feed,flatten,proto}
			if( value==null && token.liter!=null )value=token.liter;
			else{
				if(value==null)value='';
				if(convert)while(convert=convert[1])value=convert[0](value); //throws{value/target}
			}

			while(lvalues){
				var lvalue=lvalues[0];
				
				if(a=lvalue.path){
					var	i=a.length,
						entry=a[i-1],
						up=entry&&this.up,
						$=this.$,
						target;
					if(entry==null)target=this.node,i--;
					else{
						if(up||i>1)while($[0][entry]==null)$=$[1] ||daFail(entry+" not declared",this);
						target=$[0];
					}
					while(0<--i)target=target[a[i]]||(target[a[i]]={});
					if(target[a=a[0]]!=value){
						target[a]=value;
						if(up)up[entry]=$[0][entry];
					}
				}
				
				convert=lvalue.convert, flatten=lvalue.feed&&convert[0];
				if(flatten)value=this.execFeed(lvalue.feed,[],flatten,value);//throws{feed,flatten,proto}
				if(convert)while(convert=convert[1])value=convert[0](value);//throws{value/target}

				lvalues=lvalues[1];
			}
			return value;
		}
		catch(y){
			if(y.snapshot){
				if(!y.snapshot.target)y.snapshot.target=y.snapshot.token={}; // fresh async token
				y.snapshot.token.alias=token.alias;
				y.snapshot.token.convert=convert;
				y.snapshot.token.lvalues=lvalues;
			}
			throw y;
		}
	},

	
 	checkUp:
	function(event,from){
		daASYNC=false;
		var	node=this.node, P=node.P, def=P.rules.d.def, up={},
			rule=(event==true)?null:(node.rules&&node.rules[event])||P.rules.u;
		if(rule)event=this.exec(rule.todo)||event;
		for(var entry in this.up)if(!(def&&def[entry]))up[entry]=this.up[entry];
		if(node.parentNode)return(node.parentNode.P && new daExecBranch(node.parentNode,null,up).checkUp(event,node)) || daCheckDown(node,this.up,from);
	}
	
};

function daCheckDown(node,up,from){
	var d=node.P.rules.d; if(!d)return;	
	var $0=node.$[0], use=d.use, def=d.def, sift, dirty;
	
	for(var entry in up)
		if($0[entry]!=null)
		{
			dirty=dirty||(use&&use[entry]);
			$0[entry]=((sift||(sift={}))[entry]=up[entry]);//(def&&def[entry])?up[entry]:
		}
	if(dirty){
		daASYNC=true;
		var rebuilt=node.P.spawn(node.parentNode,node.$);
		try{node.parentNode.replaceChild(rebuilt,node)}catch(e){/* too late, forget it */};
	}else
		if(sift)
			for(var nodes=node.childNodes,i=nodes.length,n;i-->0;)
				if((n=nodes[i]).P && n!=from )daCheckDown(n,sift);
	return dirty;
};

function daTrace($,entry){
	if($)return($[0][entry]==null)?($[0][entry]=daTrace($[1],entry)):$[0][entry];
};


var daPhase={
	
	d:function(place,P,$){
		var n;
		if(P instanceof Array)for(var i=0;i<P.length;i++)daPhase.d(place,P[i],$);
		else if(P&&(n=P.spawn?P.spawn(place,$):P.nodeType?P:newText(P)))place.appendChild(n);
	},
	
	u:function(node,event){
		if(node)new daExecBranch(node,null,{}).checkUp(event);
		for(var i=dapost.length,p;i-->0;)p=dapost.pop(),p.handler(p.subj);
	}
};

dap.operate["--"]=function(){
	return; // debug checkpoint
}